package com.damagez.selfrefresh.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.damagez.selfrefresh.util.StringToDurationConverter;
import org.springframework.cache.Cache;
import org.springframework.cache.transaction.AbstractTransactionSupportingCacheManager;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.Collection;
import java.util.LinkedList;

/**
 * @author damage
 * @create 2020/5/9 10:14
 * @description
 */
public class RefreshRedisCacheManager extends AbstractTransactionSupportingCacheManager {

    public static final String BEAN_NAME = "refreshRedisCacheManager";

    private static final String SEPARATOR = "#";

    private static StringRedisSerializer KEY_SERIALIZER = new StringRedisSerializer();
    private static Jackson2JsonRedisSerializer<Object> VALUE_SERIALIZER = new Jackson2JsonRedisSerializer<>(Object.class);

    static {
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        VALUE_SERIALIZER.setObjectMapper(om);
    }

    /**
     * 初始加载缓存返回空，缓存创建的时候在创建
     * @return
     */
    @Override
    protected Collection<? extends Cache> loadCaches() {
        return new LinkedList<>();
    }

    private RefreshRedisCacheWriter cacheWriter;

    public RefreshRedisCacheManager(RefreshRedisCacheWriter cacheWriter) {
        this.cacheWriter = cacheWriter;
    }

    @Override
    protected RedisCache getMissingCache(String name) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
                .defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(KEY_SERIALIZER))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(VALUE_SERIALIZER));
        Duration expireTime = getExpireTime(name);
        if (expireTime != null) {
            redisCacheConfiguration = redisCacheConfiguration.entryTtl(expireTime);
        }
        Duration refreshTime = getRefreshTime(name);
        Integer refreshNeedMinInvoke = getRefreshNeedMinInvoke(name);
        if (refreshTime != null) {
            return createRedisCache(name, redisCacheConfiguration, refreshTime.toMillis(), refreshNeedMinInvoke);
        } else {
            return createRedisCache(name, redisCacheConfiguration, 0L, refreshNeedMinInvoke);
        }
    }

    public static Duration getExpireTime(String name) {
        String[] split = name.split(SEPARATOR);
        if (split.length > 1) {
            Object expireTime = StringToDurationConverter.getInstance().convert(split[1], TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Duration.class));
            return (Duration) expireTime;
        } else {
            return null;
        }
    }

    public static Duration getRefreshTime(String name) {
        String[] split = name.split(SEPARATOR);
        if (split.length > 2) {
            Duration expireTime = (Duration) StringToDurationConverter.getInstance().convert(split[1], TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Duration.class));
            Duration refreshTime = (Duration) StringToDurationConverter.getInstance().convert(split[2], TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Duration.class));
            if (expireTime.compareTo(refreshTime) < 0) {
                throw new IllegalArgumentException("缓存刷新时间必须小于缓存过期时间");
            }
            return refreshTime;
        } else {
            return null;
        }
    }

    public static Integer getRefreshNeedMinInvoke(String name) {
        String[] split = name.split(SEPARATOR);
        if (split.length > 3) {
            return Integer.parseInt(split[3]);
        } else {
            return null;
        }
    }

    protected RefreshRedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig, Long refreshMill, Integer refreshNeedMinInvoke) {
        return new RefreshRedisCache(name, cacheWriter, cacheConfig, refreshMill, refreshNeedMinInvoke);
    }
}
